/*
 * Copyright 2010 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.anim
{
	import sweezy.anim.core.IAnimationBuilder;

	public function animate(target:Object, properties:Object = null):IAnimationBuilder
	{
		return new AnimationBuilder(target, properties);
	}
}

import sweezy.anim.core.Animator;
import sweezy.anim.core.IAction;
import sweezy.anim.core.IAnimation;
import sweezy.anim.core.IAnimationBuilder;
import sweezy.anim.core.IEasing;
import sweezy.anim.core.IPropertyAnimation;
import sweezy.anim.easing.Easing;

class AnimationBuilder implements IAnimationBuilder
{

	private var _autoRewind:Boolean;

	private var _decorations:Array;

	private var _defaultProperties:Object;

	private var _delay:uint;

	private var _duration:uint;

	private var _easing:IEasing;

	private var _onComplete:Function;

	private var _onCompleteParams:Array;

	private var _onCompleteScope:Object;

	private var _onError:Function;

	private var _onErrorScope:Object;

	private var _onPause:Function;

	private var _onPauseParams:Array;

	private var _onPauseScope:Object;

	private var _onStart:Function;

	private var _onStartParams:Array;

	private var _onStartScope:Object;

	private var _onUpdate:Function;

	private var _onUpdateParams:Array;

	private var _onUpdateScope:Object;

	private var _option:Object;

	private var _properties:Object;

	private var _target:Array;

	public function AnimationBuilder(target:Object, defaultProperties:Object)
	{
		if (target is Array)
		{
			_target = target.concat();
		}
		else if (target is Vector)
		{
			_target = [];
			for (var i:int = 0, len:uint = target.length; i < len; i++)
			{
				_target[i] = target[i];
			}
		}
		else if (target === null)
		{
			_target = [];
		}
		else
		{
			_target = [target];
		}

		_defaultProperties = defaultProperties;
		_properties = {};
		_decorations = [];
	}

	public function action(action:IAction, quePosition:uint):IAnimationBuilder
	{
		return registerDecorator(Animator.action, [null, action, quePosition]);
	}

	public function get animation():IAnimation
	{
		if (_target.length === 0)
		{
			_target[0] = null;
		}

		if (_target.length === 1)
		{
			return createAnimation(_target[0], false);
		}

		var result:Array = [];
		for (var i:int = 0, len:uint = _target.length; i < len; i++)
		{
			result[i] = createAnimation(_target[i], true);
		}

		return createAnimation(Animator.parallel(result.shift(), result), false);
	}

	public function autoRewind(autoRewind:Boolean):IAnimationBuilder
	{
		_autoRewind = autoRewind;
		return this;
	}

	public function by(name:String, offset:Number, round:Boolean = false):IAnimationBuilder
	{
		return registerProperty(name, NaN, offset, round, 2);
	}

	public function delay(delay:uint):IAnimationBuilder
	{
		_delay = delay;
		return this;
	}

	public function duration(duration:uint):IAnimationBuilder
	{
		_duration = duration;
		return this;
	}

	public function easeIn(name:String):IAnimationBuilder
	{
		return easing(Easing.easeIn(name));
	}

	public function easeInOut(name:String):IAnimationBuilder
	{
		return easing(Easing.easeInOut(name));
	}

	public function easeOut(name:String):IAnimationBuilder
	{
		return easing(Easing.easeOut(name));
	}

	public function easeOutIn(name:String):IAnimationBuilder
	{
		return easing(Easing.easeOutIn(name));
	}

	public function easing(easing:IEasing):IAnimationBuilder
	{
		_easing = easing;
		return this;
	}

	public function fromBy(name:String, start:Number, offset:Number, round:Boolean = false):IAnimationBuilder
	{
		return registerProperty(name, start, offset, round, 4);
	}

	public function fromTo(name:String, start:Number, end:Number, round:Boolean = false):IAnimationBuilder
	{
		return registerProperty(name, start, end, round, 3);
	}

	public function onComplete(handler:Function, scope:Object = null, params:Array = null):IAnimationBuilder
	{
		_onComplete = handler;
		_onCompleteScope = scope;
		_onCompleteParams = params;
		return this;
	}

	public function onError(handler:Function, scope:Object = null):IAnimationBuilder
	{
		_onError = handler;
		_onErrorScope = scope;
		return this;
	}

	public function onPause(handler:Function, scope:Object = null, params:Array = null):IAnimationBuilder
	{
		_onPause = handler;
		_onPauseScope = scope;
		_onPauseParams = params;
		return this;
	}

	public function onStart(handler:Function, scope:Object = null, params:Array = null):IAnimationBuilder
	{
		_onStart = handler;
		_onStartScope = scope;
		_onStartParams = params;
		return this;
	}

	public function onUpdate(handler:Function, scope:Object = null, params:Array = null):IAnimationBuilder
	{
		_onUpdate = handler;
		_onUpdateScope = scope;
		_onUpdateParams = params;
		return this;
	}

	public function option(option:Object):IAnimationBuilder
	{
		_option = option;
		return this;
	}

	public function padding(before:uint, after:uint = 0):IAnimationBuilder
	{
		return registerDecorator(Animator.padding, [null, before, after]);
	}

	public function repeat(numRepeat:uint = 0):IAnimationBuilder
	{
		return registerDecorator(Animator.repeat, [null, numRepeat]);
	}

	public function reverse():IAnimationBuilder
	{
		return registerDecorator(Animator.reverse, [null]);
	}

	public function roundTrip():IAnimationBuilder
	{
		return registerDecorator(Animator.roundTrip, [null]);
	}

	public function scale(scale:Number):IAnimationBuilder
	{
		return registerDecorator(Animator.scale, [null, scale]);
	}

	public function slice(startPosition:uint, duration:uint = 0):IAnimationBuilder
	{
		return registerDecorator(Animator.slice, [null, startPosition, duration]);
	}

	public function start(name:String = null):IAnimation
	{
		return animation.start(name);
	}

	public function to(name:String, end:Number, round:Boolean = false):IAnimationBuilder
	{
		return registerProperty(name, NaN, end, round, 1);
	}

	private function createAnimation(target:Object, many:Boolean):IAnimation
	{
		var result:IAnimation;

		if (target === null)
		{
			result = Animator.empty(_duration);
		}
		else if (target is IAnimation)
		{
			result = target as IAnimation;
		}
		else if (target is IAnimationBuilder)
		{
			result = (target as IAnimationBuilder).animation;
		}
		else
		{
			var animation:IPropertyAnimation;
			if (_defaultProperties !== null)
			{
				var defaultProperties:Object = {base: _defaultProperties};
				if (_easing !== null)
				{
					defaultProperties.easing = _easing;
				}
				if (_duration !== 0)
				{
					defaultProperties.duration = _duration;
				}
				if (_delay !== 0)
				{
					defaultProperties.delay = _delay;
				}
				animation = Animator.animate2(target, defaultProperties);
			}
			else
			{
				animation = Animator.animate(target, _duration, _easing, _delay);
			}

			for (var name:String in _properties)
			{
				var propertyInfo:PropertyInfo = _properties[name];
				if (propertyInfo.type === 1)
				{
					animation.to(name, propertyInfo.endValue);
				}
				else if (propertyInfo.type === 2)
				{
					animation.by(name, propertyInfo.endValue);
				}
				else if (propertyInfo.type === 3)
				{
					animation.fromTo(name, propertyInfo.startValue, propertyInfo.endValue);
				}
				else if (propertyInfo.type === 4)
				{
					animation.fromBy(name, propertyInfo.startValue, propertyInfo.endValue);
				}
			}

			result = animation;
		}

		if (result is IPropertyAnimation)
		{
			var p:IPropertyAnimation = result as IPropertyAnimation;
			if (_onError !== null || _onErrorScope !== null)
			{
				p.onError = _onError;
				p.onErrorScope = _onErrorScope;
			}
			p.option = _option;
		}

		if (many)
		{
			return result;
		}

		for (var i:int = 0, len:uint = _decorations.length; i < len; i++)
		{
			var decorationInfo:DecorationInfo = _decorations[i];
			decorationInfo.args[0] = result;
			result = decorationInfo.fn.apply(null, decorationInfo.args);
		}

		result.autoRewind = _autoRewind;
		if (_onComplete !== null)
		{
			result.onComplete = _onComplete;
			result.onCompleteParams = _onCompleteParams;
			result.onCompleteScope = _onCompleteScope;
		}
		if (_onUpdate !== null)
		{
			result.onUpdate = _onUpdate;
			result.onUpdateParams = _onUpdateParams;
			result.onUpdateScope = _onUpdateScope;
		}
		if (_onStart !== null)
		{
			result.onStart = _onStart;
			result.onStartParams = _onStartParams;
			result.onStartScope = _onStartScope;
		}
		if (_onPause !== null)
		{
			result.onPause = _onPause;
			result.onPauseParams = _onPauseParams;
			result.onPauseScope = _onPauseScope;
		}

		return result;
	}

	private function registerDecorator(fn:Function, args:Array):IAnimationBuilder
	{
		var info:DecorationInfo = new DecorationInfo();
		info.fn = fn;
		info.args = args;
		_decorations.push(info);

		return this;
	}

	private function registerProperty(name:String, startValue:Number, endValue:Number, round:Boolean, type:uint):IAnimationBuilder
	{
		if (!name)
		{
			return this;
		}

		var info:PropertyInfo = _properties[name];
		if (info === null)
		{
			_properties[name] = info = new PropertyInfo();
		}
		info.type = type;
		info.startValue = startValue;
		info.endValue = endValue;
		info.round = round;

		return this;
	}
}

class DecorationInfo
{

	public var args:Array;

	public var fn:Function;
}

class PropertyInfo
{

	public var endValue:Number;

	public var round:Boolean;

	public var startValue:Number;

	public var type:uint;
}
